package cn.juque.lucenecandy.controller;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ClassLoaderUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.RandomUtil;
import cn.juque.common.base.BaseRequestVO;
import cn.juque.common.base.BaseResponseVO;
import cn.juque.common.constants.MessageEnum;
import cn.juque.common.constants.NumberConstant;
import cn.juque.common.exception.AppException;
import cn.juque.common.utils.FastJsonUtil;
import cn.juque.lucenecandy.cache.DataSyncWaitWriteMsgCache;
import cn.juque.lucenecandy.core.base.BaseEntity;
import cn.juque.lucenecandy.core.base.BaseMessageEntity;
import cn.juque.lucenecandy.core.datasync.IDataSyncService;
import cn.juque.lucenecandy.core.datasync.listener.TccControllerListenerRender;
import cn.juque.lucenecandy.core.datasync.tcc.ITccService;
import cn.juque.lucenecandy.core.enums.DocSyncTypeEnum;
import cn.juque.lucenecandy.core.utils.SignatureUtil;
import cn.juque.lucenecandy.vo.IndexUpdateParamVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @author juque
 * @version 1.0.0
 * <ul>
 *     <li>CandyController</li>
 * </ul>
 * @date 2023-12-27
 **/
@Slf4j
@Controller
@RequestMapping("/lucd")
public class CandyController {

    @Resource(name = "candyTryCommitService")
    private ITccService candyTryCommitService;

    @Resource(name = "candyCommitService")
    private ITccService candyCommitService;

    @Resource(name = "candyCancelService")
    private ITccService candyCancelService;

    @Resource
    private DataSyncWaitWriteMsgCache dataSyncWaitWriteMsgCache;

    @Resource
    private TccControllerListenerRender tccControllerListenerRender;

    /**
     * 心跳检测
     *
     * @return BaseResponseVO
     */
    @GetMapping(value = "/check")
    @ResponseBody
    public BaseResponseVO<List<String>> check() {
        long salt = System.currentTimeMillis();
        String randomStr = RandomUtil.randomString(NumberConstant.SIX);
        String signature = SignatureUtil.createSignature(salt, randomStr);
        List<String> list = CollUtil.newArrayList(Long.toString(salt), randomStr, signature);
        return new BaseResponseVO<>(MessageEnum.OK, list);
    }

    /**
     * 预提交文档
     *
     * @return BaseResponseVO
     */
    @PostMapping(value = "/tryCommit")
    @ResponseBody
    public BaseResponseVO<Boolean> tryCommit(@RequestBody BaseRequestVO<IndexUpdateParamVO> request) {
        IndexUpdateParamVO paramVO = request.getParam();
        if (Boolean.FALSE.equals(this.checkIndexParam(paramVO))) {
            return new BaseResponseVO<>(MessageEnum.OK, false);
        }
        // 事前
        this.tccControllerListenerRender.before(IDataSyncService.SyncTypeEnum.PRE, paramVO);
        Class<? extends BaseEntity> tClass = ClassUtil.loadClass(paramVO.getClassName());
        List<? extends BaseEntity> content = this.deserialize(paramVO.getContentStr(), tClass);
        DocSyncTypeEnum syncType = paramVO.getSyncType();
        switch (syncType) {
            case ADD:
                this.candyTryCommitService.add(content);
                break;
            case UPDATE:
                this.candyTryCommitService.update(content);
                break;
            case DEL:
                this.candyTryCommitService.del(paramVO.getClassName(), paramVO.getIdList());
                break;
            default:
                break;
        }
        // 事后
        this.tccControllerListenerRender.after(IDataSyncService.SyncTypeEnum.PRE, paramVO);
        return new BaseResponseVO<>(MessageEnum.OK, true);
    }

    /**
     * 生效文档
     *
     * @return BaseResponseVO
     */
    @PostMapping(value = "/commit")
    @ResponseBody
    public BaseResponseVO<Boolean> commit(@RequestBody BaseRequestVO<IndexUpdateParamVO> request) {
        IndexUpdateParamVO paramVO = request.getParam();
        if (Boolean.FALSE.equals(this.checkIndexParam(paramVO))) {
            return new BaseResponseVO<>(MessageEnum.OK, false);
        }
        // 事前
        this.tccControllerListenerRender.before(IDataSyncService.SyncTypeEnum.COMMIT, paramVO);
        Class<? extends BaseEntity> tClass = ClassUtil.loadClass(paramVO.getClassName());
        List<? extends BaseEntity> content = this.deserialize(paramVO.getContentStr(), tClass);
        DocSyncTypeEnum syncType = paramVO.getSyncType();
        switch (syncType) {
            case ADD:
                this.candyCommitService.add(content);
                break;
            case UPDATE:
                this.candyCommitService.update(content);
                break;
            case DEL:
                this.candyCommitService.del(paramVO.getClassName(), paramVO.getIdList());
                break;
            default:
                break;
        }
        // 事后
        this.tccControllerListenerRender.after(IDataSyncService.SyncTypeEnum.COMMIT, paramVO);
        return new BaseResponseVO<>(MessageEnum.OK, true);
    }

    /**
     * 取消操作
     *
     * @return BaseResponseVO
     */
    @PostMapping(value = "/cancel")
    @ResponseBody
    public BaseResponseVO<Boolean> cancel(@RequestBody BaseRequestVO<IndexUpdateParamVO> request) {
        IndexUpdateParamVO paramVO = request.getParam();
        if (Boolean.FALSE.equals(this.checkIndexParam(paramVO))) {
            return new BaseResponseVO<>(MessageEnum.OK, false);
        }
        // 事前
        this.tccControllerListenerRender.before(IDataSyncService.SyncTypeEnum.CANCEL, paramVO);
        Class<? extends BaseEntity> tClass = ClassUtil.loadClass(paramVO.getClassName());
        List<? extends BaseEntity> content = this.deserialize(paramVO.getContentStr(), tClass);
        DocSyncTypeEnum syncType = paramVO.getSyncType();
        switch (syncType) {
            case ADD:
                this.candyCancelService.add(content);
                break;
            case UPDATE:
                this.candyCancelService.update(content);
                break;
            case DEL:
                this.candyCancelService.del(paramVO.getClassName(), paramVO.getIdList());
                break;
            default:
                break;
        }
        // 事后
        this.tccControllerListenerRender.after(IDataSyncService.SyncTypeEnum.CANCEL, paramVO);
        return new BaseResponseVO<>(MessageEnum.OK, true);
    }

    @PostMapping(value = "/batchMsg")
    @ResponseBody
    public BaseResponseVO<Boolean> batchMsg(@RequestBody BaseRequestVO<String> request) {
        if (Objects.nonNull(request) && CharSequenceUtil.isNotEmpty(request.getParam())) {
            List<BaseMessageEntity> list = FastJsonUtil.parseArray(request.getParam(), BaseMessageEntity.class);
            list.forEach(f -> this.dataSyncWaitWriteMsgCache.put(null, f));
        }
        return new BaseResponseVO<>(MessageEnum.OK, true);
    }

    /**
     * 检查变量是否合法
     *
     * @param param IndexUpdateParamVO
     * @return boolean
     */
    private boolean checkIndexParam(IndexUpdateParamVO param) {
        if (Objects.isNull(param)) {
            return false;
        }
        if (CharSequenceUtil.isEmpty(param.getClassName()) || Objects.isNull(param.getSyncType())) {
            return false;
        }
        if (CharSequenceUtil.isEmpty(param.getContentStr()) && CollUtil.isEmpty(param.getIdList())) {
            if (DocSyncTypeEnum.ADD.equals(param.getSyncType()) || DocSyncTypeEnum.UPDATE.equals(param.getSyncType())) {
                return false;
            }
            if (DocSyncTypeEnum.DEL.equals(param.getSyncType())) {
                return false;
            }
        }
        if (!ClassLoaderUtil.isPresent(param.getClassName())) {
            throw new AppException(MessageEnum.SYSTEM_ERROR.getCode(), "实体类[" + param.getClassName() + "]不存在");
        }
        return true;
    }

    private List<? extends BaseEntity> deserialize(String json, Class<? extends BaseEntity> clazz) {
        List<? extends BaseEntity> list = FastJsonUtil.parseArray(json, clazz);
        return CollUtil.isEmpty(list) ? new ArrayList<>(0) : list;
    }
}
